/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package javax.swing.text.html;

import java.util.Enumeration;
import java.awt.*;
import javax.swing.SizeRequirements;
import javax.swing.border.*;
import javax.swing.event.DocumentEvent;
import javax.swing.text.*;

/**
 * A view implementation to display a block (as a box)
 * with CSS specifications.
 *
 * @author Timothy Prinzing
 */
public class BlockView extends BoxView {

  /**
   * Creates a new view that represents an
   * html box.  This can be used for a number
   * of elements.
   *
   * @param elem the element to create a view for
   * @param axis either View.X_AXIS or View.Y_AXIS
   */
  public BlockView(Element elem, int axis) {
    super(elem, axis);
  }

  /**
   * Establishes the parent view for this view.  This is
   * guaranteed to be called before any other methods if the
   * parent view is functioning properly.
   * <p>
   * This is implemented
   * to forward to the superclass as well as call the
   * {@link #setPropertiesFromAttributes()}
   * method to set the paragraph properties from the css
   * attributes.  The call is made at this time to ensure
   * the ability to resolve upward through the parents
   * view attributes.
   *
   * @param parent the new parent, or null if the view is being removed from a parent it was
   * previously added to
   */
  public void setParent(View parent) {
    super.setParent(parent);
    if (parent != null) {
      setPropertiesFromAttributes();
    }
  }

  /**
   * Calculate the requirements of the block along the major
   * axis (i.e. the axis along with it tiles).  This is implemented
   * to provide the superclass behavior and then adjust it if the
   * CSS width or height attribute is specified and applicable to
   * the axis.
   */
  protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
    if (r == null) {
      r = new SizeRequirements();
    }
    if (!spanSetFromAttributes(axis, r, cssWidth, cssHeight)) {
      r = super.calculateMajorAxisRequirements(axis, r);
    } else {
      // Offset by the margins so that pref/min/max return the
      // right value.
      SizeRequirements parentR = super.calculateMajorAxisRequirements(
          axis, null);
      int margin = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
          getTopInset() + getBottomInset();
      r.minimum -= margin;
      r.preferred -= margin;
      r.maximum -= margin;
      constrainSize(axis, r, parentR);
    }
    return r;
  }

  /**
   * Calculate the requirements of the block along the minor
   * axis (i.e. the axis orthogonal to the axis along with it tiles).
   * This is implemented
   * to provide the superclass behavior and then adjust it if the
   * CSS width or height attribute is specified and applicable to
   * the axis.
   */
  protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
    if (r == null) {
      r = new SizeRequirements();
    }

    if (!spanSetFromAttributes(axis, r, cssWidth, cssHeight)) {

            /*
             * The requirements were not directly specified by attributes, so
             * compute the aggregate of the requirements of the children.  The
             * children that have a percentage value specified will be treated
             * as completely stretchable since that child is not limited in any
             * way.
             */
/*
            int min = 0;
            long pref = 0;
            int max = 0;
            int n = getViewCount();
            for (int i = 0; i < n; i++) {
                View v = getView(i);
                min = Math.max((int) v.getMinimumSpan(axis), min);
                pref = Math.max((int) v.getPreferredSpan(axis), pref);
                if (
                max = Math.max((int) v.getMaximumSpan(axis), max);

            }
            r.preferred = (int) pref;
            r.minimum = min;
            r.maximum = max;
            */
      r = super.calculateMinorAxisRequirements(axis, r);
    } else {
      // Offset by the margins so that pref/min/max return the
      // right value.
      SizeRequirements parentR = super.calculateMinorAxisRequirements(
          axis, null);
      int margin = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
          getTopInset() + getBottomInset();
      r.minimum -= margin;
      r.preferred -= margin;
      r.maximum -= margin;
      constrainSize(axis, r, parentR);
    }

        /*
         * Set the alignment based upon the CSS properties if it is
         * specified.  For X_AXIS this would be text-align, for
         * Y_AXIS this would be vertical-align.
         */
    if (axis == X_AXIS) {
      Object o = getAttributes().getAttribute(CSS.Attribute.TEXT_ALIGN);
      if (o != null) {
        String align = o.toString();
        if (align.equals("center")) {
          r.alignment = 0.5f;
        } else if (align.equals("right")) {
          r.alignment = 1.0f;
        } else {
          r.alignment = 0.0f;
        }
      }
    }
    // Y_AXIS TBD
    return r;
  }

  boolean isPercentage(int axis, AttributeSet a) {
    if (axis == X_AXIS) {
      if (cssWidth != null) {
        return cssWidth.isPercentage();
      }
    } else {
      if (cssHeight != null) {
        return cssHeight.isPercentage();
      }
    }
    return false;
  }

  /**
   * Adjust the given requirements to the CSS width or height if
   * it is specified along the applicable axis.  Return true if the
   * size is exactly specified, false if the span is not specified
   * in an attribute or the size specified is a percentage.
   */
  static boolean spanSetFromAttributes(int axis, SizeRequirements r,
      CSS.LengthValue cssWidth,
      CSS.LengthValue cssHeight) {
    if (axis == X_AXIS) {
      if ((cssWidth != null) && (!cssWidth.isPercentage())) {
        r.minimum = r.preferred = r.maximum = (int) cssWidth.getValue();
        return true;
      }
    } else {
      if ((cssHeight != null) && (!cssHeight.isPercentage())) {
        r.minimum = r.preferred = r.maximum = (int) cssHeight.getValue();
        return true;
      }
    }
    return false;
  }

  /**
   * Performs layout for the minor axis of the box (i.e. the
   * axis orthogonal to the axis that it represents). The results
   * of the layout (the offset and span for each children) are
   * placed in the given arrays which represent the allocations to
   * the children along the minor axis.
   *
   * @param targetSpan the total span given to the view, which would be used to layout the
   * children.
   * @param axis the axis being layed out
   * @param offsets the offsets from the origin of the view for each of the child views; this is a
   * return value and is filled in by the implementation of this method
   * @param spans the span of each child view; this is a return value and is filled in by the
   * implementation of this method
   */
  protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
    int n = getViewCount();
    Object key = (axis == X_AXIS) ? CSS.Attribute.WIDTH : CSS.Attribute.HEIGHT;
    for (int i = 0; i < n; i++) {
      View v = getView(i);
      int min = (int) v.getMinimumSpan(axis);
      int max;

      // check for percentage span
      AttributeSet a = v.getAttributes();
      CSS.LengthValue lv = (CSS.LengthValue) a.getAttribute(key);
      if ((lv != null) && lv.isPercentage()) {
        // bound the span to the percentage specified
        min = Math.max((int) lv.getValue(targetSpan), min);
        max = min;
      } else {
        max = (int) v.getMaximumSpan(axis);
      }

      // assign the offset and span for the child
      if (max < targetSpan) {
        // can't make the child this wide, align it
        float align = v.getAlignment(axis);
        offsets[i] = (int) ((targetSpan - max) * align);
        spans[i] = max;
      } else {
        // make it the target width, or as small as it can get.
        offsets[i] = 0;
        spans[i] = Math.max(min, targetSpan);
      }
    }
  }


  /**
   * Renders using the given rendering surface and area on that
   * surface.  This is implemented to delegate to the css box
   * painter to paint the border and background prior to the
   * interior.
   *
   * @param g the rendering surface to use
   * @param allocation the allocated region to render into
   * @see View#paint
   */
  public void paint(Graphics g, Shape allocation) {
    Rectangle a = (Rectangle) allocation;
    painter.paint(g, a.x, a.y, a.width, a.height, this);
    super.paint(g, a);
  }

  /**
   * Fetches the attributes to use when rendering.  This is
   * implemented to multiplex the attributes specified in the
   * model with a StyleSheet.
   */
  public AttributeSet getAttributes() {
    if (attr == null) {
      StyleSheet sheet = getStyleSheet();
      attr = sheet.getViewAttributes(this);
    }
    return attr;
  }

  /**
   * Gets the resize weight.
   *
   * @param axis may be either X_AXIS or Y_AXIS
   * @return the weight
   * @throws IllegalArgumentException for an invalid axis
   */
  public int getResizeWeight(int axis) {
    switch (axis) {
      case View.X_AXIS:
        return 1;
      case View.Y_AXIS:
        return 0;
      default:
        throw new IllegalArgumentException("Invalid axis: " + axis);
    }
  }

  /**
   * Gets the alignment.
   *
   * @param axis may be either X_AXIS or Y_AXIS
   * @return the alignment
   */
  public float getAlignment(int axis) {
    switch (axis) {
      case View.X_AXIS:
        return 0;
      case View.Y_AXIS:
        if (getViewCount() == 0) {
          return 0;
        }
        float span = getPreferredSpan(View.Y_AXIS);
        View v = getView(0);
        float above = v.getPreferredSpan(View.Y_AXIS);
        float a = (((int) span) != 0) ? (above * v.getAlignment(View.Y_AXIS)) / span : 0;
        return a;
      default:
        throw new IllegalArgumentException("Invalid axis: " + axis);
    }
  }

  public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
    super.changedUpdate(changes, a, f);
    int pos = changes.getOffset();
    if (pos <= getStartOffset() && (pos + changes.getLength()) >=
        getEndOffset()) {
      setPropertiesFromAttributes();
    }
  }

  /**
   * Determines the preferred span for this view along an
   * axis.
   *
   * @param axis may be either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
   * @return the span the view would like to be rendered into &gt;= 0; typically the view is told to
   * render into the span that is returned, although there is no guarantee; the parent may choose to
   * resize or break the view
   * @throws IllegalArgumentException for an invalid axis type
   */
  public float getPreferredSpan(int axis) {
    return super.getPreferredSpan(axis);
  }

  /**
   * Determines the minimum span for this view along an
   * axis.
   *
   * @param axis may be either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
   * @return the span the view would like to be rendered into &gt;= 0; typically the view is told to
   * render into the span that is returned, although there is no guarantee; the parent may choose to
   * resize or break the view
   * @throws IllegalArgumentException for an invalid axis type
   */
  public float getMinimumSpan(int axis) {
    return super.getMinimumSpan(axis);
  }

  /**
   * Determines the maximum span for this view along an
   * axis.
   *
   * @param axis may be either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
   * @return the span the view would like to be rendered into &gt;= 0; typically the view is told to
   * render into the span that is returned, although there is no guarantee; the parent may choose to
   * resize or break the view
   * @throws IllegalArgumentException for an invalid axis type
   */
  public float getMaximumSpan(int axis) {
    return super.getMaximumSpan(axis);
  }

  /**
   * Update any cached values that come from attributes.
   */
  protected void setPropertiesFromAttributes() {

    // update attributes
    StyleSheet sheet = getStyleSheet();
    attr = sheet.getViewAttributes(this);

    // Reset the painter
    painter = sheet.getBoxPainter(attr);
    if (attr != null) {
      setInsets((short) painter.getInset(TOP, this),
          (short) painter.getInset(LEFT, this),
          (short) painter.getInset(BOTTOM, this),
          (short) painter.getInset(RIGHT, this));
    }

    // Get the width/height
    cssWidth = (CSS.LengthValue) attr.getAttribute(CSS.Attribute.WIDTH);
    cssHeight = (CSS.LengthValue) attr.getAttribute(CSS.Attribute.HEIGHT);
  }

  protected StyleSheet getStyleSheet() {
    HTMLDocument doc = (HTMLDocument) getDocument();
    return doc.getStyleSheet();
  }

  /**
   * Constrains <code>want</code> to fit in the minimum size specified
   * by <code>min</code>.
   */
  private void constrainSize(int axis, SizeRequirements want,
      SizeRequirements min) {
    if (min.minimum > want.minimum) {
      want.minimum = want.preferred = min.minimum;
      want.maximum = Math.max(want.maximum, min.maximum);
    }
  }

  private AttributeSet attr;
  private StyleSheet.BoxPainter painter;

  private CSS.LengthValue cssWidth;
  private CSS.LengthValue cssHeight;

}
